package com.starsoft.quant.strategy.bank;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.starsoft.frame.util.DateUtil;
import com.starsoft.frame.util.StringUtil;
import com.starsoft.quant.bean.QuantDetail;
import com.starsoft.quant.executor.StrategyReport;
import com.starsoft.quant.strategy.StrategyImpl;
import com.starsoft.smdc.bean.SmdcDiv;
import com.starsoft.smdc.bean.SmdcEr;
import com.starsoft.smdc.bean.SmdcMarketDaily;
import com.starsoft.smdc.bean.SmdcSecurity;
import com.starsoft.smdc.service.DivService;
import com.starsoft.smdc.service.ErService;
import com.starsoft.trade.bean.TradeAcountStatus;
import com.starsoft.trade.bean.TradeBill;
import com.starsoft.trade.bean.TradePosition;
import com.starsoft.trade.order.Order;
import com.starsoft.trade.util.TradeType;

public class BankStragegy extends StrategyImpl {
	
	private static final Logger logger = LoggerFactory.getLogger(BankStragegy.class);
	
	private double preYear;
	private double scoreDiff;
	private int erWidth;
	private int secNum;
	private int moneyDiv;
	private ErService erService;
	private DivService divService;
	
	private RoeCalculate roeCalculate;
	private Date changePosDate;
	
	@Override
	public StrategyReport handle() {
		roeCalculate = new RoeCalculate();
		changePosDate = getChangePosDate();
		
		erWidth = Integer.parseInt(modelContext.getModelParam("erWidth"));
		preYear = Double.parseDouble(modelContext.getModelParam("preYear"));
		scoreDiff = Double.parseDouble(modelContext.getModelParam("scoreDiff"));
		secNum = Integer.parseInt(modelContext.getModelParam("secNum"));
		moneyDiv = Integer.parseInt(modelContext.getModelParam("moneyDiv"));
		if(secNum>moneyDiv){
			secNum = moneyDiv;
		}
		
		erService = context.getErService();
		divService = context.getDivService();
		
		List<SmdcSecurity> securityList = context.getSecurityList();
		List<QuantDetail> details = new ArrayList<>();
		
		for (SmdcSecurity security : securityList) {
			try {
				QuantDetail report = analysis(security);
				details.add(report);
			} catch (Exception e) {
				logger.error(security.getSecId()+" " + DateUtil.toString(context.getCurrentDate()), e);
				context.getRunLog().append(security.getSecId()).append(" ");
				context.getRunLog().append(security.getSecName()).append(" ");
				context.getRunLog().append(DateUtil.toString(context.getCurrentDate())).append(" ");
				context.getRunLog().append(e.getMessage()+" ").append(e.getClass().toString()).append("\n");
			}
		}
		StrategyReport report = createReport(details);
		order(report);
		return report;
	}
	
	private QuantDetail analysis(SmdcSecurity security) {
		SmdcEr er = context.getEr(security);
		SmdcMarketDaily market = context.getMarket(security.getSecId());
		double cupon = calRoe(security);
		double reportAssetBankAdj = getShareAssertAdjBank(er);
		double presentAssert = getPV(security, cupon, context.getCurrentDate());
		double predictAssert = getFV(security, cupon, presentAssert);
		double yeild = Math.pow(predictAssert / market.getClosePrice(), 1d / preYear)*100 - 100;
		double pb = market.getClosePrice()/presentAssert;
		double pe = market.getClosePrice()/(cupon*presentAssert);
		Double changePosProfit = null;
		if(!DateUtil.isToday(changePosDate)){
			SmdcMarketDaily changePosDateMarket = context.getMarketDailyService().getMarketDaily(security, changePosDate);
			if(changePosDateMarket!=null){
				changePosProfit = market.getClosePrice()* market.getAdjFactor()/changePosDateMarket.getClosePrice()/changePosDateMarket.getAdjFactor();
				changePosProfit = (changePosProfit-1)*100;
			}
		}else {
			changePosProfit = 0.0;
		}
		
		QuantDetail report = createDetail(security, yeild);
		report.setCol1(StringUtil.toStrNull(presentAssert));
		report.setCol2(StringUtil.toStrNull(predictAssert));
		report.setCol3(StringUtil.toStrNull(cupon*100));
		report.setCol4(StringUtil.toStrNull(er.getShareAssertAdj()));
		report.setCol5(StringUtil.toStrNull(reportAssetBankAdj));
		report.setCol6(DateUtil.toString(er.getId().getReportDate()));
		report.setCol7(StringUtil.toStrNull(pb));
		report.setCol8(StringUtil.toStrNull(pe));
		report.setCol9(StringUtil.toStrNull(changePosProfit));
		report.setCol10(DateUtil.toString(changePosDate));
		
		return report;
	}

	private void order(StrategyReport report) {
		Integer acountId = context.getModelContext().getAcountId();
		TradeAcountStatus status = context.getAcountStatus();
		List<TradePosition> positions = status.getPositions();

		Map<String, Double> adjDivs = new HashMap<>();
		Map<String, Double> realDivs = new HashMap<>();
		double adjScoreSum = 0;
		QuantDetail detail0 = report.getDetail(0);
		for (int i = 0; i < secNum; i++) {
			QuantDetail detail = report.getDetail(i);
			double adjScore = scoreDiff + detail.getRating() - detail0.getRating();
			if(adjScore<0){
				adjScore = 0;
			}
			adjDivs.put(detail.getId().getSecId(), adjScore);
			adjScoreSum += adjScore;
		}
		
		Double leftCash = status.getSummary().getCash();
		Double netAsset = status.getSummary().getNetAssert();
		// sell
		for (TradePosition position : positions) {
			String secId = position.getId().getSecId();
			QuantDetail detail = report.getDetail(secId);
			if(detail==null) continue;
			Double adjDiv = scoreDiff + detail.getRating() - detail0.getRating();
			adjDiv = Math.ceil(adjDiv / adjScoreSum * moneyDiv);
			double realDiv = position.getSecValue() / netAsset * moneyDiv;
			realDivs.put(secId, realDiv);
			double rintRealDiv = Math.rint(realDiv);
			if (rintRealDiv == 0 && realDiv != 0) {
				rintRealDiv = realDiv;
			}

			if (rintRealDiv > adjDiv) {
				double sellMoney = (realDiv - adjDiv) * netAsset / moneyDiv;
				Double closePrice = context.getMarket(secId).getClosePrice();
				double num = Math.rint(sellMoney / closePrice / 100) * 100;
				if( num > position.getSecNumAfs() || adjDiv == 0){
					num = position.getSecNumAfs();
				}
				Order order = new Order(secId, -num, closePrice);
				leftCash += context.getOrderService().getOrderMoney(acountId, order);
				report.addOrder(order);
			}
		}
		// buy
		for (int i = 0; i < secNum; i++) {
			QuantDetail detail = report.getDetail(i);
			String secId = detail.getId().getSecId();
			Double realDiv = realDivs.get(secId);
			double rintRealDiv = 0;
			if (realDiv != null) {
				rintRealDiv = Math.rint(realDiv);
			} else {
				realDiv = 0.0;
				rintRealDiv = 0;
			}
			Double adjDiv = adjDivs.get(secId);
			if (adjDiv == null) {
				adjDiv = 0.0;
			} else {
				adjDiv = Math.rint(adjDiv / adjScoreSum * moneyDiv);
			}
			if(rintRealDiv<adjDiv && leftCash/netAsset*moneyDiv>0.5){
				double buyMoney = (adjDiv - realDiv) * netAsset / moneyDiv;
				if(buyMoney>leftCash) buyMoney = leftCash;
				Double closePrice = context.getMarket(secId).getClosePrice();
				double num = (int) Math.floor(buyMoney / closePrice / 100) * 100;
				Order order = new Order(secId, num, closePrice);
				leftCash += context.getOrderService().getOrderMoney(acountId, order);
				if (leftCash < 0) {
					num = num -100;
					order.setSecNum(num);
				}
				report.addOrder(order);
			}
		}
	}

	public double calRoe(SmdcSecurity security) {
		List<SmdcEr> ers = erService.getPbDateBeforeN(security, context.getCurrentDate(), erWidth + 1);
		if (ers.size() < 2) {
			throw new RuntimeException("er report not enough. less than 2");
		}
		if (ers.size() != erWidth + 1) {
			logger.warn("er report not enough. " +DateUtil.toString(context.getCurrentDate()) + security.getSecId());
		}
		SmdcEr erNew = ers.get(ers.size() - 1);
		SmdcEr erOld = ers.get(0);
		Date erNewDate = erNew.getId().getReportDate();
		Date erOldDate = erOld.getId().getReportDate();

		Cashflow cashOut = new Cashflow(getShareAssertAdjBank(erNew), erNewDate, erNew.getShareFactor());
		Date divBeginDate = DateUtil.getNext(erOldDate);
		List<SmdcDiv> divList = divService.getDivDateBetween(security, divBeginDate, erNewDate);
		List<Cashflow> cashIn = new ArrayList<Cashflow>();
		cashIn.add(new Cashflow(getShareAssertAdjBank(erOld), erOldDate, erOld.getShareFactor()));
		for (SmdcDiv div : divList) {
			Date divDate = div.getId().getDivDate();
			if (div.getAdditionalIssue() != null) {
				cashIn.add(new Cashflow(div.getAdditionalIssue(), divDate,  div.getShareFactorBf()));
			}
			if (div.getPerCashDiv() != null) {
				cashIn.add(new Cashflow(-div.getPerCashDiv(), divDate,  div.getShareFactorBf()));
			}
		}
		return roeCalculate.getRoe(cashIn, cashOut);
	}
	
	private double getPV(SmdcSecurity security, double cupon, Date fvDate){
		SmdcEr er = context.getEr(security);
		List<Cashflow> cashIn = new ArrayList<Cashflow>();
		Date divBeginDate = DateUtil.getNext(er.getId().getReportDate());
		List<SmdcDiv> divList = divService.getPbDateBefore(security, context.getCurrentDate());
		cashIn.add(new Cashflow(getShareAssertAdjBank(er), er.getId().getReportDate(), er.getShareFactor()));
		for (SmdcDiv div : divList) {
			Date divDate = div.getId().getDivDate();
			if(divDate.after(divBeginDate) && !divDate.after(fvDate)){
				Double shareFactorBf = div.getShareFactorBf();
				if (shareFactorBf == null) {
					throw new RuntimeException("No shareFactorBf");
				}
				if (div.getAdditionalIssue() != null) {
					cashIn.add(new Cashflow(div.getAdditionalIssue(), divDate,  shareFactorBf));
				}
				if (div.getPerCashDiv() != null) {
					cashIn.add(new Cashflow(-div.getPerCashDiv(), divDate,  shareFactorBf));
				}
			}
		}
		SmdcMarketDaily market = context.getMarket(security.getSecId());
		return roeCalculate.caculateFV(cashIn, cupon, market.getShareFactor(), fvDate);
	}
	
	private double getFV(SmdcSecurity security, double cupon, double pv){
		List<Cashflow> cashIn = new ArrayList<Cashflow>();
		List<SmdcDiv> divList = divService.getPbDateBefore(security, context.getCurrentDate());
		cashIn.add(new Cashflow(pv, context.getCurrentDate(), 1));
		for (SmdcDiv div : divList) {
			Date divDate = div.getId().getDivDate();
			if(divDate.after(context.getCurrentDate()) && div.getAdditionalIssue() != null){
				cashIn.add(new Cashflow(div.getAdditionalIssue(), divDate,  1));
			}
		}
		Date fvDate = DateUtil.bumpDate(context.getCurrentDate(), (int)preYear, 0, 0);
		return roeCalculate.caculateFV(cashIn, cupon, 1, fvDate);
	}

	public Double getShareAssertAdjBank(SmdcEr er) {
		Double shareAssertAdj = er.getShareAssertAdj();
		Double badLoan = er.getBadLoan();
		Double bobei = er.getBobeiRatio();
		Double shares = er.getShares();
		if(shareAssertAdj==null){
			throw new RuntimeException("调整每股净资产为空");
		}
		if(badLoan==null){
			throw new RuntimeException("坏账为空");
		}
		if(bobei==null){
			throw new RuntimeException("拨备覆盖率为空");
		}
		if(shares==null){
			throw new RuntimeException("总股本为空");
		}
		return shareAssertAdj + badLoan * (bobei - 1.5) / shares;
	}
	
	private Date getChangePosDate(){
		Integer acountId = context.getModelContext().getAcountId();
		Date startDate = context.getModelContext().getQuantAcount().getStartDate();
		Date endDate = DateUtil.getLast(context.getCurrentDate());
		List<TradeBill> bills = context.getTradeBillService().getBills(acountId, startDate , endDate);		
		
		TradeBill lastBill= null;
		for (int i = bills.size() - 1; i >= 0; i--) {
			TradeBill bill = bills.get(i);
			boolean isSellBuy = TradeType.get(bill).isSellBuy();
			if(isSellBuy && lastBill!=null){
				boolean isSameDay = DateUtil.isSameDay(bill.getBillTime(), lastBill.getBillTime());
				boolean isSameSec = bill.getSecId().equals(lastBill.getSecId());
				if(isSameDay&& !isSameSec){
					return bill.getBillTime();
				} else {
					lastBill = bill;
				}
			} else {
				lastBill = bill;
			}
		}
		return startDate;
	}

}
